package org.wheel.plugins.toolkit.util.sql.visitor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.select.SelectVisitorAdapter;
import org.wheel.module.core.request.ParamField;
import org.wheel.plugins.toolkit.jsqlparse.helper.ParamValueConvert;
import org.wheel.plugins.toolkit.jsqlparse.helper.parameter.DollarParameter;
import org.wheel.plugins.toolkit.jsqlparse.helper.parameter.MyBatisParameter;
import org.wheel.plugins.toolkit.sql.enums.SqlModel;
import org.wheel.plugins.toolkit.util.sql.SQLHelper;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

/**
 * SQL参数替换处理器
 * 使用Visitor模式处理SQL参数替换
 */
public class SqlParameterReplacementHandler {

    /**
     * 替换SQL中的参数
     * @param originalSql 原始SQL
     * @param conditions 条件列表
     * @param sqlModel SQL模型
     * @param existingParams 原始SQL中已经存在的参数列表
     * @return key: 替换后的SQL, value: 参数列表。<br>
     * Value 返回的 {@code LinkedHashMap<String, Object>} ，如果是SQL模式是原生格式或Number数值格式，返回的map格式为：<pre>{@code
     * {
     *   "1": {"fieldName": "value"},
     *   "2": { "key_1": [1, 2, 3]},
     *   "3": { "key_2: "xxxxxxxx"}
     * }}</pre>
     * 如果是其他SQL模式，返回的map格式为：<pre>{@code
     * {
     *   "fieldName": "value",
     *   "key_1": [1, 2, 3],
     *   "key_2: "xxxxxxxx"
     * }
     * }</pre>
     *
     * 外部需要调用参数的话，SQL模型是Number或者Native原生的SQL，可以通过 {@code SQLHelper.resolveParameters(LinkedHashMap<String, Object> map)}方法将参数进行转换。
     * @throws JSQLParserException 解析异常
     */
    public static Pair<String, LinkedHashMap<String, Object>> replaceParameters(String originalSql, List<ParamField> conditions, SqlModel sqlModel, Set<String> existingParams)
            throws JSQLParserException {

        // 创建字段到条件的映射
        Map<String, ParamField> fieldToCondition = new HashMap<>();
        for (ParamField condition : conditions) {
            String field = condition.getField();
            fieldToCondition.put(field, condition);
        }

        // 创建参数替换访问者
        SqlParameterReplacementVisitor visitor = new SqlParameterReplacementVisitor(fieldToCondition, sqlModel, existingParams);

        // 定义一个Select Visitor，用于处理PlainSelect中的WHERE子句
        SelectVisitorAdapter<Void> selectVisitorAdapter = new SelectVisitorAdapter<>() {
            @Override
            public <S> Void visit(net.sf.jsqlparser.statement.select.PlainSelect plainSelect, S context) {
                if(CollectionUtil.isNotEmpty(plainSelect.getSelectItems())){
                    plainSelect.getSelectItems().forEach(selectItem -> {
                        selectItem.accept(visitor, context);
                    });
                }
                if (plainSelect.getWhere() != null) {
                    plainSelect.getWhere().accept(visitor, context);
                }
                if(plainSelect.getHaving() != null){
                    plainSelect.getHaving().accept(visitor, context);
                }
                return null;
            }
        };

        // 定义一个Statement Visitor，用于分发语句
        StatementVisitorAdapter<Void> statementVisitor = new StatementVisitorAdapter<>() {
            public <S> Void visit(net.sf.jsqlparser.statement.select.Select select, S context) {
                if (select != null) {
                    return select.accept(selectVisitorAdapter, context);
                }
                return null;
            }
        };

        // 解析原始SQL
        Statement statement = CCJSqlParserUtil.parse(originalSql);

        // 应用访问者模式处理SQL
        statement.accept(statementVisitor, null);

        return Pair.of(statement.toString(), visitor.getParameterMaps());
    }

    /**
     * 统一处理参数映射处理工具类，避免多处重复处理参数映射，格式不一致
     * 返回的 parameterMaps ，如果是SQL模式是原生格式或Number数值格式，返回的map格式为：<pre>{@code
     * {
     *   "1": {"fieldName": "value"},
     *   "2": { "key_1": [1, 2, 3]},
     *   "3": { "key_2: "xxxxxxxx"}
     * }}</pre>
     * 如果是其他SQL模式，返回的map格式为：<pre>{@code
     * {
     *   "fieldName": "value",
     *   "key_1": [1, 2, 3],
     *   "key_2: "xxxxxxxx"
     * }
     * }</pre>
     * @param parameterMaps 参数映射
     * @param condition 条件
     * @param sqlModel SQL模型
     * @param startKeyIndex 参数映射的起始索引
     * @param isAddKeyIndex 是否添加索引值，对于在后面要合并进来的参数时，需要在索引值之后增加，避免索引作为parameterMaps中的key值重复，参数覆盖
     */
    public static void paramMapHandle(LinkedHashMap<String, Object> parameterMaps, Map<String, Object> condition, SqlModel sqlModel, int startKeyIndex, boolean isAddKeyIndex){
        if(sqlModel == SqlModel.NATIVE || sqlModel == SqlModel.NUMBER){
            StringBuilder keyBuilder = new StringBuilder();
            int initSize = SQLHelper.resolveParameters(parameterMaps).length+1;
            if(isAddKeyIndex) {
                initSize += startKeyIndex;
            }
            keyBuilder.append(initSize);
            condition.forEach((key, value) -> parameterMaps.put(keyBuilder.toString(), Map.of(key, value)));
        }else{
            parameterMaps.putAll(condition);
        }
    }

    /**
     * 从带参数的字符串中提取命名参数
     * @param unkownString 带参数的字符串，目前只支持 :paramName、#{paramName}、{paramName} 或 ${paramName} 4种格式
     * @return 命名参数集合
     */
    public static Set<String> extractNamedParameters(String unkownString) {
        Set<String> parameters = new HashSet<>();
        List<Pair<String, String>> pairs = new ArrayList<>();
        extractNamedParameters(unkownString, pairs);
        pairs.forEach(pair -> parameters.add(pair.getValue()));
        return parameters;
    }


    public static Collection<Pair<String, String>> extractNamedParameters(String unkownString, Collection<Pair<String, String>>  pairs){

        // 匹配 :paramName 格式
        java.util.regex.Pattern colonParamPattern = java.util.regex.Pattern.compile(":([a-zA-Z_][a-zA-Z0-9_]*)");
        java.util.regex.Matcher colonMatcher = colonParamPattern.matcher(unkownString);
        while (colonMatcher.find()) {
            pairs.add(Pair.of(colonMatcher.group(0), colonMatcher.group(1)));
        }

        // 匹配 #{paramName} 或 {paramName} 或 ${paramName}格式
        java.util.regex.Pattern mybatisParamPattern = java.util.regex.Pattern.compile("[#|$]?\\{([a-zA-Z_][a-zA-Z0-9_]*)}");
        java.util.regex.Matcher mybatisMatcher = mybatisParamPattern.matcher(unkownString);
        while (mybatisMatcher.find()) {
            pairs.add(Pair.of(mybatisMatcher.group(0), mybatisMatcher.group(1)));
        }
        return pairs;
    }

    /**
     * 从带参数的SQL中提取命名参数
     * @param unkownString 带参数的SQL
     * @return 命名参数集合 key: 原SQL中带符号的参数名称，value: 去掉特殊符号的参数名称 例如： {"#{propertyName}": "propertyName"}
     */
    public static Map<String, String> extractNamedParametersMap(String unkownString) {
        HashMap<String, String> parameters = new HashMap<>();
        java.util.regex.Pattern customParamPattern = java.util.regex.Pattern.compile("[#|$]?\\{([a-zA-Z_][a-zA-Z0-9_][0-9]*)}");
        java.util.regex.Matcher customMatcher = customParamPattern.matcher(unkownString);
        while (customMatcher.find()) {
            parameters.put(customMatcher.group(), customMatcher.group(1));
        }
        return parameters;
    }

    /**
     * 提取Native模式下的参数信息
     * @param originalSql 原始SQL
     * @return 参数信息
     * @throws JSQLParserException 解析异常
     */
    public static List<ParamField> extractNativeParameterInfo(String originalSql)
            throws JSQLParserException {

        // 创建访问者来遍历表达式并记录参数位置
        ParameterPositionVisitor visitor = new ParameterPositionVisitor();

        // 定义一个Select Visitor，用于处理PlainSelect中的WHERE子句
        SelectVisitorAdapter<Void> selectVisitorAdapter = new SelectVisitorAdapter<>() {
            @Override
            public <S> Void visit(net.sf.jsqlparser.statement.select.PlainSelect plainSelect, S context) {
                if(CollectionUtil.isNotEmpty(plainSelect.getSelectItems())){
                    plainSelect.getSelectItems().forEach(selectItem -> {
                        selectItem.accept(visitor, context);
                    });
                }
                if (plainSelect.getWhere() != null) {
                    plainSelect.getWhere().accept(visitor, context);
                }
                if(plainSelect.getHaving() != null){
                    plainSelect.getHaving().accept(visitor, context);
                }
                return null;
            }
        };

        // 定义一个Statement Visitor，用于分发语句
        StatementVisitorAdapter<Void> statementVisitor = new StatementVisitorAdapter<>() {
            public <S> Void visit(net.sf.jsqlparser.statement.select.Select select, S context) {
                if (select != null) {
                    return select.accept(selectVisitorAdapter, context);
                }
                return null;
            }
        };

        // 解析SQL语句
        Statement statement = CCJSqlParserUtil.parse(originalSql);
        statement.accept(statementVisitor, null);


        return visitor.getConditions();
    }

    /**
     * 将值转换为占位符表达式
     * 复用ParamValueConvert类中的方法处理参数
     */
    public static Expression convertToPlaceholderExpression(Object value, SqlModel sqlModel, String fieldName, AtomicInteger parameterIndex) {
        switch (sqlModel) {
            case MYBATIS:
                // 使用项目中已有的MyBatisParameter处理 #{propertyName} 格式
                return new MyBatisParameter(fieldName);
            case DIRECT_SQL:
                // 直接将值转换为表达式（使用ParamValueConvert中的方法）
                return ParamValueConvert.convertToComparisonOperator(value);
            case NUMBER:
                // 使用DollarParameter处理 $数字 格式，动态计算索引
                if(value instanceof Collection<?>  collection){
                    return new ExpressionList(collection.stream()
                            .map(item -> new DollarParameter(parameterIndex.getAndIncrement()))
                            .toList());
                }else if(value.getClass().isArray()){
                    return new ExpressionList(IntStream.range(0, Array.getLength(value))
                            .mapToObj(i -> new DollarParameter(parameterIndex.getAndIncrement()))
                            .toList());
                }else if(value instanceof Map<?,?> map){
                    return new ExpressionList(map.entrySet().stream()
                            .map(entry -> new DollarParameter(parameterIndex.getAndIncrement()))
                            .toList());
                }else {
                    return new DollarParameter(parameterIndex.getAndIncrement());
                }
            case PROPERTY_NAME:
                // 使用JdbcNamedParameter处理 :propertyName 格式
                return new JdbcNamedParameter(fieldName);
            case NATIVE:
                // 使用JdbcParameter处理 ? 格式
                if(value instanceof Collection<?>  collection){
                    return new ExpressionList(collection.stream()
                            .map(item -> new JdbcParameter())
                            .toList());
                } else if(value.getClass().isArray()){
                    return new ExpressionList(IntStream.range(0, Array.getLength(value))
                            .mapToObj(i -> new JdbcParameter())
                            .toList());
                } else if(value instanceof Map<?,?> map){
                    return new ExpressionList(map.entrySet().stream()
                            .map(entry -> new JdbcParameter())
                            .toList());
                }
                return new JdbcParameter();
            default:
                // 默认使用JdbcParameter处理
                return new JdbcParameter();
        }
    }
}
